/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package it.polimi.meteocal_pappalardo_sacchi.bean;

import it.polimi.meteocal_pappalardo_sacchi.entity.*;

import java.io.Serializable;
import java.time.LocalDate;

import javax.ejb.Stateless;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import java.time.Period;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 *
 *
 */
@Stateless
public class NotificationManager extends AbstractManager<Notification> implements Serializable {

    @PersistenceContext(unitName = "authPU")
    private EntityManager em;

    @Inject
    EventManager eventManager;
    @Inject
    UserBean userBean;
    @Inject
    ForecastBean forecastBean;
    @Inject
    WeatherManager weatherManager;

    @Override
    protected EntityManager getEntityManager() {
        return em;
    }

    public NotificationManager() {
        super(Notification.class);
    }

    public List<Notification> findNotificationByUser(MeteoCalUser user, boolean isRead) {
        String queryString = "SELECT n " +
                       "FROM NOTIFICATION n " +
                       "WHERE n.sentToUser = :user AND n.isRead = :isRead";
        Query query = em.createQuery(queryString);
        query.setParameter("user", user);
        query.setParameter("isRead", isRead);
        List<Notification> result = query.getResultList();
        Collections.sort(result, Collections.reverseOrder());
        return result;
    }

    public List<Notification> findInvitationByUser(MeteoCalUser user, boolean isRead) {
        String queryString = "SELECT i " +
                "FROM Invitation i " +
                "WHERE i.sentToUser = :user AND i.isRead = :isRead";
        Query query = em.createQuery(queryString);
        query.setParameter("user", user);
        query.setParameter("isRead", isRead);
        List<Notification> result = query.getResultList();
        Collections.sort(result, Collections.reverseOrder());
        return result;
    }

    public List<Notification> findBadWeatherNotificationByUser(MeteoCalUser user, boolean isRead) {
        String queryString = "SELECT bwn " +
                "FROM BadWeatherNotification bwn " +
                "WHERE bwn.sentToUser = :user AND bwn.isRead = :isRead";
        Query query = em.createQuery(queryString);
        query.setParameter("user", user);
        query.setParameter("isRead", isRead);
        List<Notification> result = query.getResultList();
        Collections.sort(result, Collections.reverseOrder());
        return result;
    }

    public Invitation findInvitationByUserAndEvent(MeteoCalUser user, Event event) {
        String queryString = "SELECT i " +
                "FROM Invitation i " +
                "WHERE i.sentToUser = :user AND i.relatedEvent = :event";
        Query query = em.createQuery(queryString);
        query.setParameter("user", user);
        query.setParameter("event", event);
        return (Invitation) query.getSingleResult();
    }

    public List<Notification> findNotificationByUserAndEvent(MeteoCalUser user, Event event) {
        String queryString = "SELECT n " +
                "FROM NOTIFICATION n " +
                "WHERE n.sentToUser = :user AND n.relatedEvent = :event";
        Query query = em.createQuery(queryString);
        query.setParameter("user", user);
        query.setParameter("event", event);
        List<Notification> result = query.getResultList();
        Collections.sort(result, Collections.reverseOrder());
        return result;
    }
    
    
    public void composeNotification(Notification notification, MeteoCalUser user, String message, Event event, String title){
        java.sql.Date nowDate = new java.sql.Date(java.util.Calendar.getInstance().getTime().getTime());
        java.sql.Time nowTime = new java.sql.Time(java.util.Calendar.getInstance().getTime().getTime());
        notification.setGenerationDate(nowDate);
        notification.setGenerationTime(nowTime);           
        notification.setSentToUser(user);
        notification.setTitle(title);
        notification.setMessage("Hi, " + user.getUsername() + "! " + message);
        //set relation with user
        user.getReceivedNotificationSet().add(notification);
        //set relation with event. Il controllo sul null serve perchè la notifica di delete passa null
        if (event != null) {
            event.getRelatedNotificationList().add(notification);
            notification.setRelatedEvent(event);
            create(notification);
            eventManager.edit(event);
        } else {
            create(notification);
        }
    }

    /*Event deletion notification*/
    public void sendDeletionNotification(Event event) {
        List<MeteoCalUser> invitedGuests = eventManager.findAlreadyInvitedUserList(event);
        invitedGuests.remove(userBean.getUser());
        if (invitedGuests.isEmpty()) {
            return;
        }
        String message = "Event " + event.getTitle() + " has been deleted.";
        for (MeteoCalUser user : invitedGuests) {
            Notification notification = new Notification();
            //passo null come evento perchè la notifica farà riferimento a un evento cancellato
            composeNotification(notification, user, message, null, event.getTitle());
        }
    }

    //passo una lista specifica di utenti da notificare. se passo null invece, notifico gli utenti ricavati dal db
    public void sendModificationNotification(List<MeteoCalUser> userListToNotify, Event event, Event oldEvent, String modifiedFields) {

        if (userListToNotify == null) {
            userListToNotify = eventManager.findAlreadyInvitedUserList(oldEvent);
        }

        userListToNotify.remove(userBean.getUser());

        if (userListToNotify.isEmpty()) {
            return;
        }

        if (modifiedFields.equals("")){
            return;
        }

        String message = " Event " +oldEvent.getTitle()+ " has been modified: \n" + modifiedFields;

        for (MeteoCalUser user : userListToNotify) {
            EventModificationNotification emn = new EventModificationNotification();
            //passo event e non oldevent perchè in composenotification ho edit dell'evento (per settare la chiave esterna), perciò se lo chiamo sul vecchio evento mi annulla le modifiche fatte
            composeNotification(emn, user, message, event, event.getTitle());
        }

    }

    public boolean checkIfModifiedEventNeedsImmediateBadWeatherNotification(Event oldEvent, Event newEvent) {
        System.out.println(oldEvent.getStartingDate().toLocalDate());
        System.out.println(newEvent.getStartingDate().toLocalDate());        
        System.out.println(!oldEvent.getStartingDate().toLocalDate().isEqual(newEvent.getStartingDate().toLocalDate()));
        //se l'evento è all'aperto e viene spostato ad oggi o domani
        if (newEvent.isOutdoor() &&
                eventManager.isEventStartingTodayOrTomorrow(newEvent) &&
                (!oldEvent.getStartingDate().toLocalDate().isEqual(newEvent.getStartingDate().toLocalDate()) ||
                        !oldEvent.getEndingDate().toLocalDate().isEqual(newEvent.getEndingDate().toLocalDate()))) {
            return true;
        }
        return false;
    }

    public void sendInvitationToUserList(Event event, List<MeteoCalUser> invitedUserListToNotify) {

        if (invitedUserListToNotify == null) {
            return;
        }
        for (MeteoCalUser user : invitedUserListToNotify) {
            String message = "You have been invited to " + event.toString() + " by " + event.getCreator().getUsername() + ".";
            Invitation invitation = new Invitation();
            composeNotification(invitation, user, message, event, event.getTitle());
        }
    }

    /*
     * Send bad weather notification if the event is found to have bad weather forecasts.
     */
    public void sendBadWeatherNotification(Event event) {
        String notificationMessage = new String("");

        for(WeatherForecast wf:event.getWeatherForecastList()){
            WeatherCondition fwc = wf.getWeatherCondition();
            WeatherCondition ewc = event.getWeatherCondition();
            notificationMessage += ("On the " + wf.getStartingDate() +": ");
            if (weatherManager.checkBadTemperature(ewc.getTemperature(),fwc.getTemperature())) {
                notificationMessage += (wf.getWeatherCondition().getTemperature() + " °C are expected. ");
            }
            if(weatherManager.checkBadWind(ewc.getWindStrength(), fwc.getWindStrength())) {
                notificationMessage += (wf.getWeatherCondition().getWindStrength() + " m/s wind is expected. ");
            }
            if (weatherManager.checkBadSky(ewc.isCloud(), fwc.isCloud())) {
                notificationMessage += ("Clouds are expected. ");
            }
            if (weatherManager.checkBadSky(ewc.isRain(), fwc.isRain())) {
                notificationMessage += ("Rain is expected. ");
            }
            if (weatherManager.checkBadSky(ewc.isSnow(), fwc.isSnow())) {
                notificationMessage += ("Snow is expected. ");
            }
        }

        for (MeteoCalUser u : eventManager.findAlreadyInvitedUserList(event)) {
            Notification bwn = new BadWeatherNotification();
            composeNotification(bwn, u, notificationMessage, event, event.getTitle());
        }

    }
    
    public void sendBadWeatherNotificationWithSuggestion(Event event) {
        MeteoCalUser creator = eventManager.findCreator(event);
        int eventDuration = Period.between(event.getStartingDate().toLocalDate(), event.getEndingDate().toLocalDate()).getDays();
        java.sql.Date newStartingDate = forecastBean.retrieveSuggestion(event);
        java.sql.Date newEndingDate;
        String notificationMessage = "Your event "+event.getTitle() +" has bad weather.";
        
        if(newStartingDate != null) {
            if(newStartingDate == event.getStartingDate()){
                notificationMessage += " Sorry, no favorable dates can be found.";
                newStartingDate=null;
            } else {
                newEndingDate = java.sql.Date.valueOf(newStartingDate.toLocalDate().plusDays(eventDuration));
                notificationMessage += ". New Suggested starting date: "+newStartingDate.toString() + ". Suggested ending date: "+newEndingDate.toString() + ".";
            }
        } else {
            notificationMessage += ", but rescheduling service is not available.";               
        }
        
        BadWeatherNotificationWithSuggestion bwns = new BadWeatherNotificationWithSuggestion();
        bwns.setNewStartingDate(newStartingDate);
        bwns.setNewEndingDate(newStartingDate);
        composeNotification(bwns, creator, notificationMessage, event, event.getTitle());
    }

    public List<Notification> findEventModificationNotificationByUser(MeteoCalUser user, boolean isRead) {
        String queryString = "SELECT n " +
                "FROM EventModificationNotification n " +
                "WHERE n.sentToUser = :user AND n.isRead = :isRead";
        Query query = em.createQuery(queryString);
        query.setParameter("user", user);
        query.setParameter("isRead", isRead);
        List<Notification> result = query.getResultList();
        Collections.sort(result, Collections.reverseOrder());
        return result;
    }
}
